//TODO:better drawing/PosAll handling mIsDrawing,mIsPosValid
#include <buola/gui.h>

#include <buola/image/cimage.h>
#include <buola/gui/cpropertyfield.h>
#include <buola/gui/cpropertyeditor.h>
#include <buola/image/cgc.h>
#include <buola/image/cfont.h>
#include <buola/gui/cmenu.h>
#include <buola/gui/cconfig.h>
#include <buola/image/cgradient.h>

#include <buola/widgets/cpropertylist.h>
#include <buola/widgets/cscrollbar.h>
#include <buola/widgets/cmenubar.h>
#include <buola/widgets/ctheme.h>

#define TYPE_FIELD      1
#define TYPE_STARTGROUP 2
#define TYPE_ENDGROUP   3
#define TYPE_TITLE      4

namespace buola { namespace gui {

CPLField::CPLField(CPropertyField *pField)
{
    mField=pField;
    mType=TYPE_FIELD;
    mExpanded=true;
    mVisible=false;
}

CPLField::CPLField(const std::wstring &pName)
{
    mField=nullptr;
    mType=TYPE_STARTGROUP;
    mTitle=pName;
    mExpanded=true;
    mVisible=false;
}

CPLField::CPLField()
{
    mField=nullptr;
    mType=TYPE_ENDGROUP;
    mExpanded=true;
    mVisible=false;
}

CPLField::CPLField(const img::CPicture &pImage,const std::wstring &pTitle,
            const std::wstring &pSubTitle)
{
    mField=nullptr;
    mType=TYPE_TITLE;
    mImage=pImage;
    mTitle=pTitle;
    mSubTitle=pSubTitle;
}

CPropertyList::CPropertyList()
{
    mScrollPos.x=0;
    mScrollPos.y=0;

    mSelected=nullptr;
    mEditing=nullptr;
    mEditor=nullptr;

    mIsPosValid=false;
    mIsDrawing=true;
    mIconStyle=CIcon::MEDIUM;
    get_default_theme()->SetBackground(this);
    SetBackColor(img::rgb8(240,240,240));

    SetPointer(CPointer::STOCK_DEFAULT);

    mVScroll=new CScrollBar();
    mVScroll->eChanged.Connect(&CPropertyList::OnVScroll,this,_1);
    mVScroll->SetRange(0,15);
    mVScroll->SetStep(2,10);
    mVScroll->SetValue(0,false);
    Place(mVScroll);

///\todo    attach_scroll_bars(nullptr,mVScroll);
}

CPropertyList::~CPropertyList()
{
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
////////////////////////// Interface //////////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CPropertyList::AddTitle(const img::CPicture &pImage,const std::wstring &pTitle,
                    const std::wstring &pSubTitle)
{
    CPLField *lField=new CPLField(pImage,pTitle,pSubTitle);
    mFields.push_back(lField);

    mIsPosValid=false;
    Refresh();
}

CPLField *CPropertyList::AddField(CPropertyField *pField,const UAny &pClosure)
{
    CPLField *lField=new CPLField(pField);
    
    pField->mType->InitValue(lField->mValue);
    lField->mClosure=pClosure;
    
    mFields.push_back(lField);
    mHashTable[pField->mName]=lField;

    mIsPosValid=false;
    Refresh();
    
    return lField;
}

CPLField *CPropertyList::AddFieldAndValue(CPropertyField *pField,
                    const UAny &pValue,const UAny &pClosure)
{
    CPLField *lField=new CPLField(pField);
    
    lField->mValue=pValue;
    lField->mClosure=pClosure;
    
    mFields.push_back(lField);
    mHashTable[pField->mName]=lField;

    mIsPosValid=false;
    Refresh();
    
    return lField;
}

void CPropertyList::RemoveAllFields()
{
    mSelected=nullptr;
    if(mEditing)
        EndEditing();
    mFields.clear();
    mHashTable.clear();
    mIsPosValid=false;
    Refresh();
}

void CPropertyList::AddFields(std::vector<CPropertyField*> &pFields,
                const UAny &pClosure)
{
    for(int i=0;i<pFields.size();i++)
        AddField(pFields[i],pClosure);
        
    mIsPosValid=false;
    Refresh();
}

void CPropertyList::AddFields(std::vector<std::unique_ptr<CPropertyField>> &pFields,
                        const UAny &pClosure)
{
    for(int i=0;i<pFields.size();i++)
        AddField(pFields[i].get(),pClosure);
        
    mIsPosValid=false;
    Refresh();
}

void CPropertyList::AddFieldsAndValues(const 
            std::map<CPropertyField*,UAny> &pFields,const UAny &pClosure)
{
    for(std::map<CPropertyField*,UAny>::const_iterator i=pFields.begin();
                            i!=pFields.end();++i)
    {
        AddFieldAndValue(i->first,i->second,pClosure);
    }
        
    mIsPosValid=false;
    Refresh();
}

void CPropertyList::SetValue(const std::wstring &pName,const UAny &pValue)
{
    CPLField *lField=mHashTable[pName];
    if(!lField)
    {
        std::string lString("field name ");
        lString+=utf8(pName);
        lString+=" not found in CPropertyList::SetValue";
        throw XNotFound(lString);
    }
    lField->mValue=pValue;

    Refresh();
}

void CPropertyList::SetValue(CPropertyField *pField,const UAny &pValue)
{
    for(int i=0;i<mFields.size();i++)
    {
        if(mFields[i]->mType!=TYPE_FIELD) continue;
        if(mFields[i]->mField==pField)
        {
            mFields[i]->mValue=pValue;
        }
    }

    Refresh();
}

void CPropertyList::StartGroup(const std::wstring &pName,bool pExpanded)
{
    CPLField *lField=new CPLField(pName);
    lField->mExpanded=pExpanded;
    mFields.push_back(lField);

    mIsPosValid=false;
    Refresh();
}

void CPropertyList::EndGroup()
{
    CPLField *lField=new CPLField();
    mFields.push_back(lField);

    mIsPosValid=false;
    Refresh();
}

void CPropertyList::CollapseGroup(const std::wstring &pName)
{
    for(int i=0;i<mFields.size();i++)
    {
        if(mFields[i]->mType!=TYPE_STARTGROUP) continue;
        if(mFields[i]->mTitle!=pName) continue;
        
        if(!mFields[i]->mExpanded) continue;
        mFields[i]->mExpanded=false;
        sExpanded(pName,false);
        mIsPosValid=false;
        Refresh();
    }
}

void CPropertyList::ExpandGroup(const std::wstring &pName)
{
    for(int i=0;i<mFields.size();i++)
    {
        if(mFields[i]->mType!=TYPE_STARTGROUP) continue;
        if(mFields[i]->mTitle!=pName) continue;
        
        if(mFields[i]->mExpanded) continue;
        mFields[i]->mExpanded=true;
        sExpanded(pName,true);
        mIsPosValid=false;
        Refresh();
    }
}

void CPropertyList::Center(CPLField *pField)
{
    CPoint_d lPoint=pField->mPos+pField->mSize/2-Size()/2;
    
    mVScroll->SetValue((int)lPoint.y);
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
///////////////////// Internal functions //////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CPropertyList::DrawAll(img::CGC *pGC)
{
    mIsDrawing=true;

    for(int i=0;i<mFields.size();i++)
    {
        CPLField *lField=mFields[i];

        if(lField->mPos.x-mScrollPos.x>Size().x||
           lField->mPos.x-mScrollPos.x+lField->mSize.x<0||
           lField->mPos.y-mScrollPos.y>Size().y||
           lField->mPos.y-mScrollPos.y+lField->mSize.y<0) continue;
        
        DrawField(pGC,lField);
    }

    mIsDrawing=false;
}

void CPropertyList::DrawAll(img::CGC *pGC,const CRect_d &pRect)
{
    mIsDrawing=true;

    for(int i=0;i<mFields.size();i++)
    {
        CPLField *lField=mFields[i];

        if(lField->mPos.x-mScrollPos.x>pRect.r||
           lField->mPos.x-mScrollPos.x+lField->mSize.x<pRect.l||
           lField->mPos.y-mScrollPos.y>pRect.b||
           lField->mPos.y-mScrollPos.y+lField->mSize.y<pRect.t) continue;
        
        DrawField(pGC,lField);
    }

    mIsDrawing=false;
}

void CPropertyList::DrawField(img::CGC *pGC,CPLField *pField)
{
    if(!mIsPosValid)
        PosAll();

    if(!pField->mVisible) return;

    CPoint_d lPos=pField->mPos-mScrollPos;
    CSize_d lSize=pField->mSize;

    if(pField->mType==TYPE_FIELD)
    {
        if(pField==mSelected)
            pGC->SetStockFont(L"nicebold");
        else
            pGC->SetStockFont(L"nice");
        pGC->SetSource(gConfig.mColors.Get((pField==mSelected)?L"selection_text":L"edit_text"));
        pGC->DrawText(CPoint_d(lPos.x+mTextSize.x-5,lPos.y+lSize.y/2),pField->mField->mTitle+L":",
                      img::ETextPos::RIGHT|img::ETextPos::VCENTER);

        pGC->SetStockFont(L"nice");
        pGC->SetSource(img::CColor(L"midnightblue"));
        pField->mField->mType->Draw(pField->mValue,pGC,
                    CRect_d(CPoint_d(pField->mPos.x-mScrollPos.x+mTextSize.x+1,
                           pField->mPos.y-mScrollPos.y),
                    CSize_d(pField->mSize.x-1-mTextSize.x,pField->mSize.y-1)));
    }
    else if(pField->mType==TYPE_STARTGROUP)
    {
        pGC->SetSource(img::rgb8(115,115,115));
        pGC->SetStockFont(L"nicebold");
        pGC->DrawText(CPoint_d(lPos.x+20,lPos.y+lSize.y/2),pField->mTitle,img::ETextPos::VCENTER);

        CPoint_d lEP(lPos+CPoint_d(10,lSize.y/2));
        if(pField->mExpanded)
        {
            pGC->Path().MoveTo(CPoint_d(lEP.x-3.5,lEP.y-2.5));
            pGC->Path().LineTo(CPoint_d(lEP.x+4.5,lEP.y-2.5));
            pGC->Path().LineTo(CPoint_d(lEP.x+0.5,lEP.y+4.0));
            pGC->Path().Close();
        }
        else
        {
            pGC->Path().MoveTo(CPoint_d(lEP.x-2.5,lEP.y-3.5));
            pGC->Path().LineTo(CPoint_d(lEP.x-2.5,lEP.y+4.5));
            pGC->Path().LineTo(CPoint_d(lEP.x+4.0,lEP.y+0.5));
            pGC->Path().Close();
        }
        
        pGC->SetLineWidth(1.0);
        pGC->SetSource(img::rgb8(115,115,115));
        pGC->FillPath(true);
        pGC->StrokePath();
    }
    else if(pField->mType==TYPE_TITLE)
    {
        double lX=lPos.x+20.0;
        double lY=lPos.y+(lSize.y-10)/2;
        if(pField->mImage)
        {
            pGC->DrawImage(CPoint_d(lX,lPos.y+10.0),pField->mImage);
            lX+=pField->mImage.Size().x+20;
        }
        if(pField->mSubTitle.length())
        {
            pGC->SetStockFont(L"nicebold");
            pGC->SetSource(img::CColor(L"steelblue"));
            pGC->DrawText(CPoint_d(lX,lY+5),pField->mSubTitle);
        }
        pGC->SetStockFont(L"nicetitle");
        pGC->SetSource({0.0,0.0,0.0});
        pGC->DrawText(CPoint_d(lX,lY),pField->mTitle);
        pGC->SetLineWidth(1.0);
        pGC->Path().MoveTo(CPoint_d(lPos.x+5.0,lPos.y+lSize.y-4.5));
        pGC->Path().LineTo(CPoint_d(lPos.x+lSize.x-5.0,lPos.y+lSize.y-4.5));
        pGC->StrokePath();
    }
}

#define PLHT_NONE 0
#define PLHT_TITLE 1
#define PLHT_VALUE 2
#define PLHT_EXPAND 3

int CPropertyList::HitTestField(CPLField *pI,const CPoint_d &pP)
{
    if(!mIsPosValid)
        PosAll();

    if(!pI->mVisible) return PLHT_NONE;

    CPoint_d lP=pP-pI->mPos+mScrollPos;
    
    if(lP.x<0||lP.y<0||lP.x>=pI->mSize.x||lP.y>=pI->mSize.y)
        return PLHT_NONE;
    
    if(pI->mType==TYPE_FIELD)
    {
        if(lP.x<mTextSize.x)
            return PLHT_TITLE;
        else
            return PLHT_VALUE;
    }
    else if(pI->mType==TYPE_STARTGROUP)
    {
        if(lP.x<20)
            return PLHT_EXPAND;
    }

    return PLHT_NONE;
}

CPLField *CPropertyList::GetFieldAt(const CPoint_d &pPoint,int *pWhere)
{
    for(int i=0;i<mFields.size();i++)
    {
        int lHT=HitTestField(mFields[i],pPoint);

        if(!lHT) continue;
            
        if(pWhere)
            *pWhere=lHT;

        return mFields[i];
    }
    
    if(pWhere)
        *pWhere=PLHT_NONE;
    return nullptr;
}

void CPropertyList::ShapeField(CPLField *pI,img::PFont pFont)
{
    if(pI->mType==TYPE_FIELD)
    {
        pI->mSize={Size().x,mTextSize.y+4};
        mTextSize.x=max(mTextSize.x,25+pFont->GetWidth(pI->mField->mTitle+L":"));
    }
    else if(pI->mType==TYPE_STARTGROUP)
        pI->mSize={Size().x,mTextSize.y+12};
    else if(pI->mType==TYPE_ENDGROUP)
        pI->mSize={Size().x,0};
    else if(pI->mType==TYPE_TITLE)
    {
        if(pI->mImage)
        {
            pI->mSize={Size().x,pI->mImage.Size().y+30.0};
        }
        else if(pI->mSubTitle.length())
        {
            pI->mSize={Size().x,50};
        }
        else
        {
            pI->mSize={Size().x,38};
        }
    }
}

void CPropertyList::ShapeAll()
{
//Icon size
    if(GetStyle(STYLE_NOICON))
        mIconSize={0,0};
    else
        mIconSize=CSize_d(mIconStyle);

    img::PFont lFont=img::CFont::GetStock(L"nice");

    mTextSize.x=25;
    mTextSize.y=lFont->GetHeight();

    for(int i=0;i<mFields.size();i++)
        ShapeField(mFields[i],lFont);
}

void CPropertyList::PosAll()
{
    ShapeAll();
    
    double lY=0;

    bool lVisible=true;

    for(int i=0;i<mFields.size();i++)
    {
        mFields[i]->mPos.x=0;
        mFields[i]->mPos.y=lY;
        mFields[i]->mVisible=true;

        if(mFields[i]->mType==TYPE_STARTGROUP)
        {
            lVisible=mFields[i]->mExpanded;
        }
        else if(mFields[i]->mType==TYPE_FIELD)
        {
            if(!lVisible)
            {
                mFields[i]->mVisible=false;
                continue;
            }
        }
        lY+=mFields[i]->mSize.y;
    }
    
    int lScrollRange=(int)(lY-Size().y-1);
    
    if(lScrollRange<=0)
    {
        mVScroll->SetRange(0,0);
        mVScroll->Hide();
    }
    else
    {
        mVScroll->SetRange(0,lScrollRange);
        mVScroll->SetStep((int)mTextSize.y,(int)Size().y+1);
        mVScroll->Show();
    }
    
    mIsPosValid=true;
}

void CPropertyList::Update()
{
    PosAll();
}

void CPropertyList::StartEditing(CPLField *pI)
{
    mEditing=pI;
    
    mEditor=mEditing->mField->mType->GetEditor();
    mEditor->sEndEdit.Connect(&CPropertyList::EndEditing,this);
    mEditor->Start(pI->mValue,this,CRect_d(CPoint_d(pI->mPos.x-
        mScrollPos.x+mTextSize.x+1,pI->mPos.y-mScrollPos.y),
        CSize_d(pI->mSize.x-1-mTextSize.x,pI->mSize.y-1)),img::rgb8(240,240,240));
}

void CPropertyList::EndEditing()
{
    img::PFont lFont=img::CFont::GetStock(L"nice");
    ShapeField(mEditing,lFont);
    mEditor->Finish(mEditing->mValue);
    eChanged(mEditing->mField,mEditing->mValue,mEditing->mClosure);
    delete mEditor;
    mEditing=nullptr;
    mEditor=nullptr;
    Refresh();
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/////////////////////// Event handlers ////////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CPropertyList::OnPreExpose()
{
    if(!mIsPosValid)
        PosAll();
}
    
void CPropertyList::OnExpose(const CExposeEvent &pE)
{
    img::CGC *lGC=pE.mGC;
    DrawAll(lGC,pE.mRect);
}    

void CPropertyList::OnVScroll(int pPos)
{
    //int lScrollBy=(int)mScrollPos.y-pPos;
    mScrollPos.y=pPos;
    ///\todo
    //if(lScrollBy&&!mIsDrawing)
    //    ScrollBy(CPoint_d(0,lScrollBy));
}

void CPropertyList::OnButtonDown(const CMouseEvent &pE)
{
    if(mEditing)
    {
        EndEditing();
    }

    if(pE.mButton==EMouseButton::LEFT)
    {
        int lWhere;
        CPLField *lItem=GetFieldAt(pE.mPoint,&lWhere);     

        if(lItem)
        {
            if(mSelected!=lItem)
            {
                mSelected=lItem;
                if(mSelected->mType==TYPE_FIELD)
                    sSelected(mSelected->mField);
                Refresh();
            }
            if(lItem->mType==TYPE_FIELD)
            {
                if(mEditing!=lItem&&!(lItem->mField->mStyle&CPropertyField::PLF_READONLY))
                {
                    StartEditing(lItem);
                }
            }
            else if(lItem->mType==TYPE_STARTGROUP)
            {
                if(lWhere==PLHT_EXPAND)
                {
                    lItem->mExpanded=!lItem->mExpanded;
                    sExpanded(lItem->mTitle,lItem->mExpanded);
                    mIsPosValid=false;
                    Refresh();
                }
            }
        }
        else if(mSelected)
        {
            mSelected=nullptr;
            Refresh();
        }
    }
    else if(pE.mButton==EMouseButton::RIGHT)
    {
        CPLField *lItem=GetFieldAt(pE.mPoint);
        
        if(lItem&&lItem->mType==TYPE_FIELD)
        {
            if(GetStyle(STYLE_USERMENU))
            {
                PMenu lMenu=new CMenu;
                sFillMenu(lMenu,lItem->mField,true,lItem->mClosure);
                if(lMenu->size())
                {
                    ///\todo
                    //PMenuBar lMenuBar=new CMenuBar;
                    //lMenuBar->CreatePopup(this,lMenu,pE->mRootPoint);
                    //lMenuBar->Show();
                }
            }
        }
    }
}

void CPropertyList::OnButtonUp(const CMouseEvent &pE)
{
    ///\todo
/*  if(pE->mButton==EMouseButton::LEFT)
    {
        if(mDragging)
        {
            mDragging=false;
        }
        else if(mSelecting)
        {
            if(mLastDragPoint!=mDragPoint)
            {
//              mGC->SetFunction(GCF_INVERT);
//              mGC->DrawArea(mDragPoint.x,mDragPoint.y,mLastDragPoint.x,mLastDragPoint.y);
//              mGC->SetFunction();

                RefreshRect(CRect_d(mDragPoint,mLastDragPoint));
            }
    
            mSelecting=false;
        }
    }
*/
}

//void CPropertyList::OnDoubleClick(CMouseEvent *pE)
//{
/*  if(pE->mButton==EMouseButton::LEFT)
    {
        mDragging=false;
        mSelecting=false;
        CPLField *lItem=GetItemAt(pE->mPoint);      

        if(lItem)
        {
            OnItemDblClk(lItem);
        }
    }
*/
//}

void CPropertyList::OnMouseMove(const CMouseEvent &pE)
{
    ///\todo
}

void CPropertyList::OnMouseWheel(const CMouseEvent &pE)
{
    if(pE.mScrollY!=0.0)
        mVScroll->Scroll(pE.mScrollY);
}

void CPropertyList::OnSize(const CGeometryEvent &pE)
{
    mIsPosValid=false;

    if(mEditing)
    {
        mEditor->Finish(mEditing->mValue);
        mEditor->Start(mEditing->mValue,this,CRect_d(
        CPoint_d(mEditing->mPos.x-mScrollPos.x+mTextSize.x+1,
                mEditing->mPos.y-mScrollPos.y),
        CSize_d(mEditing->mSize.x-1-mTextSize.x,mEditing->mSize.y-1)),img::rgb8(240,240,240));
    }
}

void CPropertyList::OnStyleChanged(TStyle)
{
//  Refresh();
}

void CPropertyList::SetIconStyle(int pStyle)
{
    if(pStyle!=mIconStyle)
    {
        mIconStyle=pStyle;
        mIsPosValid=false;
                  
        Refresh();
    }
}


/*namespace gui*/ } /*namespace buola*/ }
